﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace ESpace
{
    [ToolboxBitmap(typeof(ETcpServer), "TcpServer.bmp")]
    [DefaultEvent("OnConnect")]
    public class ETcpServer : Component
    {
        #region *** внутренние переменные *********************************
        TcpListener Server;
        // параллельный лист массивов принимаемых данных
        List<byte[]> bufL = new List<byte[]>();
        Control ctrl;
        /// <summary>Все пришедшие данные (может быть часть телеграммы или несколько телеграмм подряд)</summary>
        List<List<byte>> recvDataList = new List<List<byte>>();
        #endregion *** внутренние переменные ******************************

        #region *** свойства **********************************************
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        public String LocalPoint { get { if (Server != null)return Server.LocalEndpoint.ToString(); else return ""; } }
        String pLocalIP = "127.0.0.1";
        [DefaultValue("127.0.0.1")]
        public String LocalIP
        {
            get { return pLocalIP; }
            set { if (Server == null)pLocalIP = value; }
        }
        int pLocalPort = 654;
        [DefaultValue(654)]
        public int LocalPort
        {
            get { return pLocalPort; }
            set { if (Server == null)pLocalPort = value; }
        }
        int pPackSize = 102400; // пазмер пакета
        [DefaultValue(102400)]
        public int MaxPackSize
        {
            get { return pPackSize; }
            set
            { if (Server == null)pPackSize = value; }
        }

        public Control ControlMainThread
        {
            get { return ctrl; }
            set { ctrl = value; Log.MainCtrl = value; }
        }
        #endregion *** свойства *******************************************

        // лист сокетов клиентов
        public List<Socket> Clients = new List<Socket>();

        #region *** LOG **************************************************************
        TcpLog Log;
        public String[] GetLog()
        {
            return Log.GetLog();
        }
        #endregion *** LOG **************************************************************

        public ETcpServer()
        {
            Log = new TcpLog(ctrl);
        }

        #region *** события **************************************************

        #region *** событие приема данных *************************
        // класс аргументов для события приема данных
        public class RecvEventArgs : EventArgs
        {
            public int indClient;
            public byte[] buf;
            public RecvEventArgs(int ind, byte[] buf)
            {
                indClient = ind;
                this.buf = buf;
            }
        }
        // делегат для события передачи данных
        public delegate void OnReciveDelegate(Object Sender, RecvEventArgs e);
        // переменная делегата (то бишь название функции), которая будет вызываться 
        public event OnReciveDelegate Recive;

        // генерация события приема данных
        delegate void OnReciveEventDelegate(RecvEventArgs e);
        void OnRecive(RecvEventArgs e)
        {
            // если для делегата есть функция, вызываем ее
            if (Recive != null)
                Recive(this, e);
        }
        #endregion *** событие приема данных *************************

        #region *** событие коннекта нового клиента ***************
        // класс аргументов для события
        public class ConnectEventArgs : EventArgs
        {
            public int indClient;
            public ConnectEventArgs(int ind)
            {
                indClient = ind;
            }
        }
        // делегат для события
        public delegate void OnConnectDelegate(Object Sender, ConnectEventArgs e);
        // переменная делегата (то бишь название функции), которая будет вызываться 
        public event OnConnectDelegate ClientConnect;

        // генерация события
        delegate void OnConnectEventDelegate(int ind);
        void OnConnect(int ind)
        {
            // если для делегата есть функция, вызываем ее
            if (ClientConnect != null)
                ClientConnect(this, new ConnectEventArgs(ind));
        }
        #endregion *** событие коннекта нового клиента ***************

        #region *** событие дисконнескта клиента ******************
        // класс аргументов для события
        public class DisconnectEventArgs : EventArgs
        {
            public EndPoint endPoint;
            public bool notConnect;
            public DisconnectEventArgs(EndPoint Addr, bool NotConnect)
            {
                endPoint = Addr;
                notConnect = NotConnect;
            }
        }
        // делегат для события
        public delegate void OnDisconnectDelegate(Object Sender, DisconnectEventArgs e);
        // переменная делегата (то бишь название функции), которая будет вызываться 
        public event OnDisconnectDelegate ClientDisconnect;

        // генерация события
        delegate void OnDisconnectEventDelegate(EndPoint Addr, bool NotConnect);
        public void OnDisconnect(EndPoint Addr, bool NotConnect)
        {
            // если для делегата есть функция, вызываем ее
            if (ClientDisconnect != null)
                ClientDisconnect(this, new DisconnectEventArgs(Addr, NotConnect));
        }
        #endregion *** событие дисконнескта клиента ******************

        #endregion *** события **************************************************

        #region *** работа с сервером **************************************
        /// <summary>Старт сервера. Возвращает запустился ли сервер</summary>
        public bool Start()
        {
            try
            {
                IPAddress LocalHost = ETcp.GetAddr(pLocalIP);
                if (LocalHost == null)
                {
                    Log.WriteLog("Ошибка старта сервера: неверная строка локального ip-адреса");
                    return false;
                }
                Server = new TcpListener(ETcp.GetAddr(pLocalIP), pLocalPort);
                // стартуем сервер
                Server.Start();
                // начинаем прием соединений (передаем фию события окончания приема)
                Server.BeginAcceptSocket(new AsyncCallback(EndAcceptSocket), Server);
                Log.WriteLog("Запущен TCP сервер, порт " + pLocalPort);
                return true;
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка старта сервера: " + ex.Message);
                Server = null;
                return false;
            }
        }

        // окончание приема соединения и установка подключения        
        void EndAcceptSocket(IAsyncResult ia)
        {
            try
            {
                // передаем ссылку на сервер
                TcpListener server = (TcpListener)ia.AsyncState;
                // находим сокет клиента
                Socket client = server.EndAcceptSocket(ia);
                client.ReceiveTimeout = -1;
                // добавляем клиента
                if (ctrl.InvokeRequired)
                {
                    ctrl.Invoke(new AddClientDelegate(AddClient), client);
                }
                else
                {
                    AddClient(client);
                }

                // снова запускаем на прослушку
                server.BeginAcceptSocket(new AsyncCallback(EndAcceptSocket), server);
            }
            catch
            {
                // сюда зайдет при закрытии сервера
            }
        }

        /// <summary>Остановка сервера и закрытие всех подключений. 
        /// Возвращает удачно ли остановлен сервер</summary>
        public bool Stop()
        {
            try
            {
                Server.Stop();
                Server = null;
                for (int i = 0; i < Clients.Count; i++)
                {
                    if (Clients[i] != null)
                    {
                        Clients[i].Close();
                        Clients[i] = null;
                    }
                }
                Clients.Clear();
                GC.Collect();
                Log.WriteLog("TCP сервер на " + pLocalIP.ToString() + ", порт " + pLocalPort.ToString() + " закрыт");
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка остановки сервера: " + ex.Message);
                return false;
            }
            return true;
        }

        #endregion *** работа с сервером **************************************

        #region *** работа с клиентами **************************************

        //добавление клиента
        delegate void AddClientDelegate(Socket client);
        void AddClient(Socket client)
        {
            Log.WriteLog("Подключен клиент " + client.RemoteEndPoint.ToString() + " на TCP порт " + pLocalPort);
            Clients.Add(client);
            // вызываем событие подключения
            OnConnect(Clients.Count - 1);
            bufL.Add(new byte[pPackSize]);
            recvDataList.Add(new List<byte>());
            Socket c = Clients[Clients.Count - 1];
            byte[] buf = bufL[Clients.Count - 1];
            // снова начинаем принимать данные от клиента
            client.BeginReceive(buf, 0, pPackSize, SocketFlags.None, new AsyncCallback(EndReceive), c);
        }

        //завершение процесса чтения из Socket
        void EndReceive(IAsyncResult ia)
        {
            try
            {
                Socket client = (Socket)ia.AsyncState;
                if (client == null)
                {
                    Log.WriteLog("Прием данных: не найден клиент");
                }
                // длина телеграммы
                int len = client.EndReceive(ia);
                int ind = Clients.IndexOf(client); // индекс для буфера
                if (ind == -1)
                {
                    Log.WriteLog("Прием данных: клиент " + client.RemoteEndPoint.ToString() + ",от которого пришел пакет, среди подключенных клиентов не найден ");
                }
                // если длина пакета = 0 (клиент отключился) удаляем клиент
                if (len == 0)
                {
                    Log.WriteLog("Разорвана связь с клиентом " + client.RemoteEndPoint.ToString());
                    if (ctrl.InvokeRequired)
                        ctrl.Invoke(new DeleteClientDelegate(DeleteClient), new object[2] { ind, false });
                    else DeleteClient(ind, false);
                }
                else
                {
                    byte[] recv = new byte[len];
                    Array.Copy(bufL[ind], 0, recv, 0, recv.Length);
                    Log.WriteLog("Прием данных: приняты данные длиной " + len.ToString() + " от " + client.RemoteEndPoint.ToString());
                    recvDataList[ind].AddRange(recv);
                    GetTelegramm(ind);
                    // снова ожидаем передачи данных
                    client.BeginReceive(bufL[ind], 0, pPackSize, SocketFlags.None, new AsyncCallback(EndReceive), client);
                }
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка приема данных. Ошибка приложения: " + ex.Message);
            }
        }

        /// <summary>Получение одной или нескольких телеграмм из общего буфера</summary>
        void GetTelegramm(int clInd)
        {
            while (recvDataList[clInd].Count > 0)
            {
                if (recvDataList[clInd].Count == 0) return;
                if (recvDataList[clInd].Count < 4)
                {
                    Log.WriteLog("Ошибка разбора телеграммы: В буфере менее 4-х байт");
                    recvDataList[clInd].Clear();
                    return;
                }
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream(recvDataList[clInd].ToArray()))
                using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
                {
                    int len = br.ReadInt32();
                    if (len > pPackSize)
                    {
                        Log.WriteLog("Ошибка разбора телеграммы: слишком большая длина телеграммы в буфере " + len);
                        recvDataList[clInd].Clear();
                        return;
                    }

                    if (recvDataList[clInd].Count >= (len + 4))
                    {
                        byte[] telData = new byte[len];
                        br.Read(telData, 0, len);
                        recvDataList[clInd].RemoveRange(0, len + 4);
                        Log.WriteLog("Прием телеграммы: принята телеграмма длиной " + len.ToString());
                        // вызов события принятия телеграммы
                        ctrl.Invoke(new OnReciveEventDelegate(OnRecive), new RecvEventArgs(clInd, telData));
                    }
                    else
                        return;
                }
            }
        }

        //удаление клиента
        delegate void DeleteClientDelegate(int ind, bool NotConn);
        void DeleteClient(int ind, bool NotConn)
        {
            if (ind < 0 || ind >= Clients.Count)
            {
                Log.WriteLog("ошибка удаления клиента: клиента с id " + ind.ToString() + " нет в листе клиентов");
                return;
            }
            // вызываем событие дисконнекта
            EndPoint remClPoint = Clients[ind].RemoteEndPoint;
            Log.WriteLog("Сервер TCP:" + pLocalPort + " - отключен клиент " + Clients[ind].RemoteEndPoint.ToString());
            Clients[ind].Close();
            Clients.RemoveAt(ind);
            bufL.RemoveAt(ind);
            recvDataList.RemoveAt(ind);
            OnDisconnect(remClPoint, NotConn);
        }

        /// <summary>Посылка телеграммы клиенту. Возвращает результат посулки
        /// Eсли данные клиенту отправить не удалось (ошибка 2) клиент автоматически удаляется из списка клиентов.
        /// При этом вызывается событие дисконнекта клиента
        /// </summary>
        public SendAns Send(byte[] buf, int ind)
        {
            // смотрим индекс клиента, которому отправить телеграмму
            if (ind < 0 || ind >= Clients.Count)
            {
                Log.WriteLog("не найден индекс клиента (" + ind.ToString() + "), которому отправляются данные.");
                return SendAns.NoClient;
            }
            try
            {
                byte[] sendData;
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
                using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms))
                {
                    bw.Write(buf.Length);
                    bw.Write(buf);
                    ms.SetLength(buf.Length + 4);
                    sendData = ms.ToArray();
                }
                // отправляем данные
                int nn = Clients[ind].Send(sendData);
                Log.WriteLog("отправлены данные клиенту " + Clients[ind].RemoteEndPoint.ToString() + ", длиной " + sendData.Length.ToString() + " " + nn.ToString());
                return SendAns.Ok;
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка отправки данных клиенту: " + ex.Message);
                // разрываем соединение
                if (ctrl.InvokeRequired)
                    ctrl.Invoke(new DeleteClientDelegate(DeleteClient), new object[2] { ind, true });
                else DeleteClient(ind, true);
                return SendAns.Failure;
            }
        }

        /// <summary>Отключает клиента</summary>
        public void DisconnectClient(int ind)
        {
            DeleteClient(ind, false);
        }

        #endregion *** работа с клиентами **************************************

    }

    public enum SendAns
    {
        /// <summary> 0 (Ok) - данные отправлены</summary>
        Ok,
        /// <summary>1 (NoClient) - не найден индекс клиента, которому отправляются данные;</summary>
        NoClient,
        /// <summary>2 (Failure) - не удалось отправить клиенту данные</summary>
        Failure
    };

}
